#include "src/pbc/transform/portable-transformer.h"

#include "include/v8-version.h"

namespace v8 {
namespace internal {
using namespace portable;

bool PbcTransformer::IsFuncTransformed(uint32_t func_id) {
  auto sfi = GetFuncData(func_id);
  if (sfi.is_null()) {
    return false;
  }
  return sfi->is_compiled();
}

Handle<SharedFunctionInfo> PbcTransformer::Run() {
#ifdef V8_OS_OHOS
  v8::internal::StdoutStream{} << "[pbc] PbcTransformer::Run \n";
#endif
  TRACE_EVENT0("v8," TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.AOT.Transform");
  CHECK(v8::internal::kFunctionLiteralIdTopLevel ==
        pbc_script_.GetTopLevelFuncId());
  if (lazy_transform_) {
#ifdef V8_OS_OHOS
    v8::internal::StdoutStream{} << "[pbc] Top Transform and Lazy Transform \n";
#endif
    return TransformFunction(v8::internal::kFunctionLiteralIdTopLevel);
  }

  Handle<SharedFunctionInfo> top_sfi;
  functions_to_transform_.push_back(pbc_script_.GetTopLevelFuncId());
  while (!functions_to_transform_.empty()) {
    uint32_t func_id = functions_to_transform_.back();
    functions_to_transform_.pop_back();
    if (IsFuncTransformed(func_id)) {
      continue;
    }
    Handle<SharedFunctionInfo> sfi = TransformFunction(func_id);
    if (func_id == pbc_script_.GetTopLevelFuncId()) {
      top_sfi = sfi;
    }
  }
  return top_sfi;
}

// Transform top-level function first, then inner function
Handle<SharedFunctionInfo> PbcTransformer::TransformFunction(uint32_t func_id) {
  DCHECK(func_id < pbc_script_.NumberOfFunctions());
  DCHECK(!IsFuncTransformed(func_id));

  TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.AOT.TransformFunction", "func_id", func_id);

  PbcFunction pbc_func = pbc_script_.GetFunction(func_id);

  if (i::v8_flags.log_pbc) {
    std::cout << "========== Start TransformFunction: ";
    StringRef func_name = pbc_func.GetName();
    if (func_name.Kind() == PbcValueKind::kU8String) {
      std::cout << pbc_script_.GetString(func_name);
    } else {
      std::u16string_view str_view = pbc_script_.GetU16String(func_name);
      for (auto c : str_view) {
        std::wcout << static_cast<wchar_t>(c);
      }
    }
    std::cout << ", func_id: " << func_id << std::endl;
  }

  // 1.get or create sfi
  auto sfi = GetOrCreateFuncData(func_id);
  DCHECK(!sfi->is_compiled());

  // 2.set bytecode array
  Handle<BytecodeArray> bytecode_array = TransformBytecode(func_id);
  initSourcePositionTable(bytecode_array);
  sfi->set_bytecode_array(*bytecode_array);
  CHECK(static_cast<uint32_t>(bytecode_array->parameter_count()) ==
        pbc_func.GetParameterCount());

  // update sfi
  return sfi;
}

Handle<TrustedFixedArray> PbcTransformer::TransformConstantPool(
    uint32_t func_id) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.AOT.TransformConstantPool");
  PbcFunction pbcFunc = pbc_script_.GetFunction(func_id);
  PbcConstantTable constantTable = pbcFunc.GetConstantTable(pbc_script_);
  uint32_t numEntries = constantTable.NumberOfEntries();

#if V8_MAJOR_VERSION == 11
  auto fixed_array =
      isolate_.factory()->NewFixedArray(static_cast<int>(numEntries));
#else
  auto fixed_array = isolate_.factory()->NewTrustedFixedArray(
      static_cast<int>(numEntries), AllocationType::kTrusted);
#endif
  int array_index = 0;
  bool is_short_value = constantTable.IsShortValue();
  for (uint32_t i = 0; i < numEntries; ++i) {
    Handle<Object> value;
    if (is_short_value) {
      const PbcShortValue& entry = constantTable.ShortEntries()[i];
      value = PbcShortValueToV8Value(entry);
      // Update functionsToTransform
      if (entry.Kind() == PbcShortValueKind::kShortFunction &&
          !lazy_transform_) {
        functions_to_transform_.push_back(
            static_cast<const PbcShortFunction&>(entry).GetFuncId());
      }
    } else {
      const PbcValue& entry = constantTable.Entries()[i];
      value = PbcValueToV8Value(entry);
      // Update functionsToTransform
      if (entry.Kind() == PbcValueKind::kFunction && !lazy_transform_) {
        functions_to_transform_.push_back(
            static_cast<const FunctionRef&>(entry).GetFuncId());
      }
    }
    fixed_array->set(array_index++, *value);
  }
  // TODO need consider padding that leaves holes where reservations led to
  // unused slots?
  DCHECK_GE(array_index, fixed_array->length());
  return fixed_array;
}

#if 0
// see HandlerTableBuilder::ToHandlerTable
Handle<TrustedByteArray> PbcTransformer::TransformExceptionTable(
    uint32_t func_id) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.AOT.TransformExceptionTable");

  PbcFunction pbcFunc = pbc_script_.GetFunction(func_id);
  PbcExceptionTable exceptionTable = pbcFunc.GetExceptionTable(pbc_script_);
  int handler_table_size = static_cast<int>(exceptionTable.NumberOfEntries());
#if V8_MAJOR_VERSION == 11
  Handle<TrustedByteArray> table_byte_array = isolate_.factory()->NewByteArray(
      HandlerTable::LengthForRange(handler_table_size));
#else
  Handle<TrustedByteArray> table_byte_array =
      isolate_.factory()->NewTrustedByteArray(
          HandlerTable::LengthForRange(handler_table_size),
          AllocationType::kTrusted);
#endif
  HandlerTable table(*table_byte_array);
  const ExceptionEntry* entries = exceptionTable.Entries();
  for (int i = 0; i < handler_table_size; ++i) {
    const ExceptionEntry& entry = entries[i];
    // TODO do we need catch prediction?
    HandlerTable::CatchPrediction pred = HandlerTable::UNCAUGHT;
    ;
    table.SetRangeStart(i, static_cast<int>(entry.start_));
    table.SetRangeEnd(i, static_cast<int>(entry.end_));
    table.SetRangeHandler(i, static_cast<int>(entry.handler_.Offset()), pred);
    // TODO:(wwq) Fix data
    table.SetRangeData(i, entry.data_);
  }
  return table_byte_array;
}

Handle<FeedbackMetadata> PbcTransformer::TransformFeedbackMeta(
    uint32_t func_id) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.AOT.TransformFeedbackMeta");

  PbcFunction pbc_func = pbc_script_.GetFunction(func_id);
  PbcFeedbackMeta pbc_feedback_meta = pbc_func.GetFeedbackMeta(pbc_script_);
  const int slot_count = pbc_feedback_meta.NumberOfKinds();
  const int create_closure_slot_count =
      pbc_feedback_meta.NumberOfClosureParameterCounts();
  if (slot_count == 0 && create_closure_slot_count == 0) {
    return isolate_.factory()->empty_feedback_metadata();
  }
  Handle<FeedbackMetadata> metadata = isolate_.factory()->NewFeedbackMetadata(
      slot_count, create_closure_slot_count);

  // Initialize the slots. The raw data section has already been pre-zeroed in
  // NewFeedbackMetadata.
  for (int i = 0; i < slot_count; i++) {
    FeedbackSlot slot(i);
    // TODO:(wwq) check FeedbackSlotKind consistency
    FeedbackSlotKind kind =
        static_cast<FeedbackSlotKind>(pbc_feedback_meta.GetKind(i));
    metadata->SetKind(slot, kind);
  }

#if V8_MAJOR_VERSION == 13
  for (int i = 0; i < create_closure_slot_count; i++) {
    uint16_t parameter_count = pbc_feedback_meta.GetClosureParameterCount(i);
    metadata->SetCreateClosureParameterCount(i, parameter_count);
  }
#endif
  return metadata;
}
#endif

Handle<BytecodeArray> PbcTransformer::TransformBytecode(uint32_t func_id) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.AOT.TransformBytecode");

  auto sfi = GetFuncData(func_id);
  DCHECK(!sfi.is_null());
  PbcFunction pbcFunc = pbc_script_.GetFunction(func_id);
  PbcBytecode pbcBytecode = pbcFunc.GetBytecode(pbc_script_);
#if 0
  if (pbcFunc.IsCopyable()) {
    // FIXME check bytecode size range
    int bytecodeSize = static_cast<int>(pbcBytecode.BytecodeSizeInBytes());
    const uint8_t* bytecodeStart = pbcBytecode.FirstBytecode();
    auto constantPool = TransformConstantPool(func_id);
    int frameSize = pbcFunc.GetRegisterCount() * kSystemPointerSize;
    int parameterCount = pbcFunc.GetParameterCount();
    auto handlerTable = TransformExceptionTable(func_id);
#if V8_MAJOR_VERSION == 11
    Handle<BytecodeArray> bytecodeArray = isolate_.factory()->NewBytecodeArray(
        bytecodeSize, bytecodeStart, frameSize, parameterCount, constantPool);
#else
    Handle<BytecodeArray> bytecodeArray = isolate_.factory()->NewBytecodeArray(
        bytecodeSize, bytecodeStart, frameSize, parameterCount, parameterCount,
        constantPool, handlerTable);
#endif
    bytecodeArray->set_handler_table(*handlerTable);
    auto new_target_or_generator = interpreter::Register(
        pbcFunc.GetIncomingNewTargetOrGeneratorRegister());
    bytecodeArray->set_incoming_new_target_or_generator_register(
        new_target_or_generator);
    // build feedback metadata
    Handle<FeedbackMetadata> feedback_metadata = TransformFeedbackMeta(func_id);
#ifdef OBJECT_PRINT
    if (i::v8_flags.log_pbc) {
      feedback_metadata->HeapObjectPrint(std::cout);
    }
#endif
     sfi->set_feedback_metadata(*feedback_metadata, kReleaseStore);
    return bytecodeArray;
  }
#endif
  PbcConstantTable constantTable = pbcFunc.GetConstantTable(pbc_script_);
  Handle<TrustedFixedArray> constantPool = TransformConstantPool(func_id);
  portable::PBCLowerItems items(&isolate_, sfi, pbcFunc, pbcBytecode,
                                constantTable, constantPool);
  return portable::PBCLower::Lower(items);
}

Handle<ScopeInfo> PbcTransformer::GetOrTransformScope(uint32_t scope_id) {
  DCHECK(scope_id != ScopeRef::Invalid().GetScopeId());
  if (!scope_data_vec_[scope_id].is_null()) {
    // ScopeInfo has been created before, just return
    return scope_data_vec_[scope_id];
  }

  TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("v8.compile"), "V8.AOT.TransformScope",
               "scope_id", scope_id);

  if (i::v8_flags.log_pbc) {
    std::cout << "TransformScope:\n";
    // pbc_scope_vec_[scope_id].Dump(pbc_script_);
    pbc_script_.GetPbcScope(scope_id).Dump(pbc_script_);
  }

  // create ScopeInfo
  PbcScope pbcScope =
      pbc_script_.GetPbcScope(scope_id);  // pbc_scope_vec_[scope_id];
  uint32_t pbcOuterScopeId = pbcScope.GetOuterScopeId();
  MaybeHandle<ScopeInfo> outerScopeInfo = kNullMaybeHandle;
  if (pbcOuterScopeId != ScopeRef::Invalid().GetScopeId()) {
    outerScopeInfo = GetScopeInfoFromPbcScope(pbcOuterScopeId);
    // outer ScopeInfo is always created before innner ScopeInfo
    outerScopeInfo.Check();
  }
  Handle<ScopeInfo> scopeInfo = ScopeInfo::Create(&isolate_, &zone_, pbcScope,
                                                  pbc_script_, outerScopeInfo);
  DCHECK(scope_data_vec_[scope_id].is_null());
  scope_data_vec_[scope_id] = scopeInfo;
  return scopeInfo;
}

Handle<SharedFunctionInfo> PbcTransformer::GetOrCreateFuncData(
    uint32_t func_id) {
  CHECK(func_id < func_data_vec_.size());
  if (!func_data_vec_[func_id].is_null()) {
    return func_data_vec_[func_id];
  }

  // create sfi
  PbcFunction pbc_func = pbc_script_.GetFunction(func_id);
  Handle<String> func_name =
      UtilsInternalizeStringRef(pbc_script_, isolate_, pbc_func.GetName());
  FunctionKind kind = ConvertFunctionKind(pbc_func.GetFunctionKind());
#if V8_MAJOR_VERSION == 11
  Handle<SharedFunctionInfo> sfi;
#else
  DirectHandle<SharedFunctionInfo> sfi;
#endif
  if (IsTopLevelFunction(func_id)) {
    // TODO:(wwq) set correct resource name
    auto resource_name = UtilsInternalizeString(isolate_, filename_);
    auto dummy_source = UtilsInternalizeString(isolate_, dummy_source_);
    auto option = ScriptOriginOptions();
    ScriptDetails script_details(resource_name, option);

#if V8_MAJOR_VERSION == 11
    auto maybe_function_info = Compiler::GetSharedFunctionInfoForScript(
        &isolate_, dummy_source, script_details,
        v8::ScriptCompiler::CompileOptions(),
        ScriptCompiler::NoCacheReason::kNoCacheNoReason, NOT_NATIVES_CODE);
#else
    ScriptCompiler::CompilationDetails compilation_details;
    auto maybe_function_info = Compiler::GetSharedFunctionInfoForScript(
        &isolate_, dummy_source, script_details,
        v8::ScriptCompiler::CompileOptions(),
        ScriptCompiler::NoCacheReason::kNoCacheNoReason, NOT_NATIVES_CODE,
        &compilation_details);
#endif

    if (!maybe_function_info.ToHandle(&sfi)) {
      FATAL("failed to get function info");
      UNREACHABLE();
    }
    sfi->set_builtin_id(i::Builtin::kCompileLazy);
    setScriptFromSfi(sfi);
    CHECK(!script_.is_null());
    // Reset correct Script::infos, eval runtime will use it
    Handle<WeakFixedArray> infos(isolate_.factory()->NewWeakFixedArray(
        static_cast<uint32_t>(func_data_vec_.size() + 1),
        AllocationType::kOld));
    initScriptInfo(infos);

    // unset the default feedback metadata
    sfi->set_raw_outer_scope_info_or_feedback_metadata(
        ReadOnlyRoots(&isolate_).the_hole_value(), SKIP_WRITE_BARRIER);
  } else {
    int16_t len = pbc_func.GetLength();
    DCHECK(len >= 0);
    MaybeHandle<String> maybe_name =
        pbc_func.IsNullName() ? MaybeHandle<String>() : func_name;
#if V8_MAJOR_VERSION == 11
    sfi = isolate_.factory()->NewSharedFunctionInfoForBuiltin(
        maybe_name, Builtin::kCompileLazy, kind);
    sfi->set_length(len);
#else
    sfi = isolate_.factory()->NewSharedFunctionInfoForBuiltin(
        maybe_name, Builtin::kCompileLazy, len, kDontAdapt, kind);
#endif
  }

  DCHECK(func_id < getScriptInfosLength());
#if V8_MAJOR_VERSION == 11
  script_->shared_function_infos().Set(
      func_id, MaybeObject::MakeWeak(MaybeObject::FromObject(*sfi)));
  func_data_vec_[func_id] = sfi;
#else
  script_->infos()->set(func_id, *sfi);
  func_data_vec_[func_id] = sfi.operator->();
#endif
  initSharedFunctionInfo(func_id, func_data_vec_[func_id]);
  return func_data_vec_[func_id];
}

}  // namespace internal
}  // namespace v8
